package com.huzp.net.mysql.jdbc;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.util.Iterator;


@Slf4j
public class MysqlSocket {
    static SelectorProvider provider = SelectorProvider.provider();
    static Selector selector = null;
    private SocketChannel client;
    private boolean connected = false;

    public MysqlSocket(HostInfo hostInfo) throws SQLException {
        init(hostInfo);
    }

    private void init(HostInfo hostInfo) throws SQLException {
        try {
            selector = provider.openSelector();
            client = provider.openSocketChannel();
            client.configureBlocking(false);//   非阻塞模式
            SelectionKey key = client.register(selector, 0, null); // 注册
            if (client.connect(new InetSocketAddress(hostInfo.getHost(), hostInfo.getPort()))) {
                log.info("mysql database [{}:{}] connected", hostInfo.getHost(), hostInfo.getPort());
                key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); // 监听读就绪和写就绪（准备写数据）
            } else {
                key.interestOps(SelectionKey.OP_CONNECT); // 监听连接就绪
            }
            while (true) {
                selector.select(); // 监听就绪事件
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while (it.hasNext()) {
                    key = it.next();
                    it.remove(); // 从已选择键集移除key
                    if (key.isConnectable()) { // 连接就绪
                        client.finishConnect(); // 完成连接
                        System.out.println("connected...");
                        key.interestOps(key.interestOps() & ~SelectionKey.OP_CONNECT); // 取消监听连接就绪（否则selector会不断提醒连接就绪）
                        key.interestOps(key.interestOps() | SelectionKey.OP_READ | SelectionKey.OP_WRITE); // 监听读就绪和写就绪
                    } else {

                        if (key.isValid()) {
                            // 如果为可读状态,读取服务端返回的数据
                            if (key.isReadable()) {
                                readMysqlVersion();
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            throw new SQLException("连接失败", e);
        }
    }

    private void readMysqlVersion() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(4); // 分配HeapByteBuffer
        int len = client.read(buffer);
        //读取消息头
        if (len > 0) {
            int length = byteArrayToInt(buffer.array());
            buffer = ByteBuffer.allocate(length);
            int read = client.read(buffer);
            if (read > 0) {
                ShakeInfo shakeInfo = new ShakeInfo(buffer);
                System.out.println(shakeInfo.toString());
            }
        }

    }

    public static int byteArrayToInt(byte[] b) {
        return b[0] & 0xFF | (b[1] & 0xFF) << 8 | (b[2] & 0xFF) << 16;
    }

    public void write(String msg) throws IOException {
        byte[] bytes = msg.getBytes(Charset.forName("UTF-8"));
        ByteBuffer buffer = ByteBuffer.allocate(bytes.length); // 建立HeapByteBuffer（DirectByteBuffer以后有机会再讨论）
        buffer.put(bytes);
        buffer.flip(); // 切换为读模式
        client.write(buffer);
    }

    public int read() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配HeapByteBuffer
        int len = client.read(buffer); // 直到没有数据 || buffer满
        if (len > 0)
            System.out.println(new String(buffer.array(), 0, len, Charset.forName("UTF-8"))); // buffer.array()：取HeapByteBuffer中的原始byte[]
        return len;
    }

    public void close() {
        try {
            if (client != null) {
                client.close();
            }
            if (selector != null) {
                selector.close();
            }
            log.info("mysql client closed!");
        } catch (IOException e) {
        }
    }
}
